package com.xb.loan.token.factory;

import com.google.common.base.Strings;
import com.xb.loan.log.exception.BizException;
import com.xb.loan.token.factory.exception.ITokenServiceException;
import com.xb.loan.token.factory.utils.Base62;
import com.xb.loan.token.factory.utils.EhcacheUtil;
import com.xb.loan.token.factory.utils.TokenInfo;
import com.xb.loan.util.log.DefaultLoggerFacotry;
import org.slf4j.Logger;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;


/**
 * 校验token工具类
 * User: erin
 * Date: 15/5/5
 * Time: 上午11:06
 */

public class VerifyTokenHandler {

    private static Logger logger = DefaultLoggerFacotry.getLogger(VerifyTokenHandler.class);
    public static int ACCESS_TOKEN_EXPIRSED = 1 * 30 * 24 * 60 * 60;//暂定1个月
//    @Resource
//    MemberMapper memberMapper;

    /**
     * 弱校验token有效性
     *
     * @param token    token
     * @param deviceId deviceId
     * @return 解出来的用户唯一标识uid
     */
    public Long weakVerifyToken(String token, String deviceId) throws BizException {

        logger.debug("weakVerifyToken,token:{},deviceId:{}", token, deviceId);

        if (Strings.isNullOrEmpty(token) || token.length() > 30 || Strings.isNullOrEmpty(deviceId) || deviceId.length() > 50) {
            logger.warn("[weakVerifyToken]param is error,token:{},deviceId:{}", token, deviceId);
            throw ITokenServiceException.TOKEN_NO_EXIST;
        }
        //step1 : token self check
        TokenInfo tokenInfo = checkToken(token);
        if (tokenInfo == null) {
            logger.warn("tokenInfo is empty,token:{},deviceId:{}", token, deviceId);
            throw ITokenServiceException.TOKEN_NO_EXIST;
        }
        boolean isVerify = tokenInfo.isOK();
        if (!isVerify) {
            logger.warn("isVerify is false,token:{},deviceId:{}", token, deviceId);
            throw ITokenServiceException.TOKEN_NO_EXIST;
        }
        String localKey = token + deviceId;
        int version = tokenInfo.getVersion();
        Long uid = 0L;
        //step2 : local cache check
        String value = EhcacheUtil.get(localKey);

        logger.debug("weakVerifyToken,token:{},deviceId:{},version:{},localKey:{},value:{}",
                new Object[]{token, deviceId, version, localKey, value});

        if (!Strings.isNullOrEmpty(value) && value.split("\\|").length > 0) {//if cache key not expired,result depends on <isLogin> of value
            boolean isLogin = Boolean.valueOf(value.split("\\|")[1]);
            //step2.1 : cache value is false,user need login !!!
            if (!isLogin) {
                logger.warn("isLogin is false,token:{},deviceId:{}", token, deviceId);
//                throw new ServiceDataException(ResultJson.ERR_NEED_LOGIN_REQUIRE);
            }
            //step2.2 : cache value is true,return uid.
            if (version == TokenVersionEnum.VERSION_1.getValue()) {
                logger.debug("weakVerifyToken token version 1.");
            } else if (version == TokenVersionEnum.VERSION_2.getValue()) {
                logger.debug("weakVerifyToken token version 2.");
                uid = tokenInfo.getUid();
            } else {
                logger.error("weakVerifyToken token version error.token:{},deviceId:{}", token, deviceId);
                throw ITokenServiceException.TOKEN_NO_EXIST;
            }
        } else {

        }

        logger.debug("weakVerifyToken,token:{},deviceId:{},uid:{},version:{},localKey:{},value:{}",
                new Object[]{token, deviceId, uid, version, localKey, value});

        return uid;
    }

    /**
     * 弱校验token有效性
     *
     * @param token    token
     * @param deviceId deviceId
     * @return 解出来的用户唯一标识uid
     */
    public Long weakVerifyToken2Url(String token, String deviceId) {

        logger.debug("weakVerifyToken,token:{},deviceId:{}", token, deviceId);

        if (Strings.isNullOrEmpty(token) || token.length() > 30 || Strings.isNullOrEmpty(deviceId) || deviceId.length() > 50) {
            logger.warn("[weakVerifyToken]param is error,token:{},deviceId:{}", token, deviceId);
            return 0L;
        }
        //step1 : token self check
        TokenInfo tokenInfo = checkToken(token);
        if (tokenInfo == null) {
            logger.warn("tokenInfo is empty,token:{},deviceId:{}", token, deviceId);
            return 0L;
        }
        boolean isVerify = tokenInfo.isOK();
        if (!isVerify) {
            logger.warn("isVerify is false,token:{},deviceId:{}", token, deviceId);
//            throw new ServiceDataException(ERR_NEED_LOGIN_REQUIRE);
            return 0L;
        }
        String localKey = token + deviceId;
        int version = tokenInfo.getVersion();
        Long uid = 0L;
        //step2 : local cache check
        String value = EhcacheUtil.get(localKey);

        logger.debug("weakVerifyToken,token:{},deviceId:{},version:{},localKey:{},value:{}",
                new Object[]{token, deviceId, version, localKey, value});

        if (!Strings.isNullOrEmpty(value) && value.split("\\|").length > 0) {//if cache key not expired,result depends on <isLogin> of value
            boolean isLogin = Boolean.valueOf(value.split("\\|")[1]);
            //step2.1 : cache value is false,user need login !!!
            if (!isLogin) {
                logger.warn("isLogin is false,token:{},deviceId:{}", token, deviceId);
//                throw new ServiceDataException(ResultJson.ERR_NEED_LOGIN_REQUIRE);
            }
            //step2.2 : cache value is true,return uid.
            if (version == TokenVersionEnum.VERSION_1.getValue()) {
                logger.debug("weakVerifyToken token version 1.");

//                Map<String,Object> searchMap = new HashMap<>();
//                searchMap.put("token",token);
//                searchMap.put("deviceId", deviceId);
//                List<Member> members = memberMapper.selectList(searchMap);
//                if(members !=null && members.size() == 1){
//                    Member member = members.get(0);
//                    uid = member.getId();
//                }else{
//
//                    logger.warn("userDeviceTokenDTO is empty,version:{},token:{},deviceId:{}", version, token, deviceId);
////                    throw new ServiceDataException(ERR_NEED_LOGIN_REQUIRE);
//                }
            } else if (version == TokenVersionEnum.VERSION_2.getValue()) {
                logger.debug("weakVerifyToken token version 2.");
                uid = tokenInfo.getUid();
            } else {
                logger.error("weakVerifyToken token version error.token:{},deviceId:{}", token, deviceId);
//                throw new ServiceDataException(ERR_NEED_LOGIN_REQUIRE);
            }
        } else {// if cache key expired or illegal,then should check redis or db.
            //step3 : check redis or db
//            Map<String,Object> searchMap = new HashMap<>();
//            searchMap.put("token",token);
//            searchMap.put("deviceId", deviceId);
//            List<Member> members = memberMapper.selectList(searchMap);
//            if(members !=null && members.size() == 1){
//
//                Member  userDeviceTokenDTO = members.get(0);
//                if (userDeviceTokenDTO == null || userDeviceTokenDTO.getId() == null) {
//                    logger.error("userDeviceTokenDTO is empty,version:{},token:{},deviceId:{}", version, token, deviceId);
//                    throw new ServiceDataException(ERR_NEED_LOGIN_REQUIRE);
//                }
//                //step4 : rewrite to cache from redis or db
//                uid = userDeviceTokenDTO.getId();
//                String cacheValue = uid + "|" + true;
//                EhcacheUtil.set(localKey, cacheValue);
//            }

        }

        logger.debug("weakVerifyToken,token:{},deviceId:{},uid:{},version:{},localKey:{},value:{}",
                new Object[]{token, deviceId, uid, version, localKey, value});

        return uid;
    }

//    /**
//     * 强校验token有效性
//     *
//     * @param token    token
//     * @param deviceId deviceId
//     * @return 用户token对象
//     * @throws ServiceDataException
//     */
//    public UserDeviceTokenDTO strictVerifyToken(String token, String deviceId) throws ServiceDataException {
//
//        logger.debug("strictVerifyToken,token:{},deviceId:{}", token, deviceId);
//
//        if (Strings.isNullOrEmpty(token) || token.length() > 30 || Strings.isNullOrEmpty(deviceId) || deviceId.length() > 50) {
//            logger.warn("[strictVerifyToken]param is error,token:{},deviceId:{}", token, deviceId);
//            throw new ServiceDataException(ERR_NEED_LOGIN_REQUIRE);
//        }
//        //token自校验
//        TokenInfo tokenInfo = checkToken(token);
//        if (tokenInfo == null) {
//            logger.warn("check token is empty:deviceId:{},uid:{}", deviceId);
//            throw new ServiceDataException(ERR_NEED_LOGIN_REQUIRE);
//        }
//        boolean isOK = tokenInfo.isOK();
//        if (!isOK) {
//            throw new ServiceDataException(ERR_NEED_LOGIN_REQUIRE);
//        }
//        //获取用户信息
//        UserDeviceTokenDTO userDeviceTokenDTO = tokenServerServiceImpl.getUserInfoByToken(token, deviceId);
//        if (userDeviceTokenDTO == null) {
//            logger.warn("miss token:deviceId:{},uid:{}", deviceId);
//            throw new ServiceDataException(ERR_NEED_LOGIN_REQUIRE);
//        }
//        return userDeviceTokenDTO;
//    }

    /**
     * 检测token是否正确
     * 1.校验版本
     * 2.自校验是否正确
     * 3.是否过有效期
     *
     * @param token token
     * @return token的有效性对象
     */
    public TokenInfo checkToken(String token) {
        try {
            //检测token是否为空
            if (Strings.isNullOrEmpty(token) || token.length() > 30) {
                logger.info("[checkToken]param is error,token:{}", token);
                return null;
            }
            TokenInfo ti = new TokenInfo();
            //校验版本是否是支持的版本
            if (!checkVersion(token)) {
                ti.setOK(false);
                return ti;
            }
            //token自校验
            if (!checkTokenSelf(token)) {
                ti.setOK(false);
                return ti;
            }
            //token是否过有效期
            if (!checkTokenExpDate(token)) {
                ti.setOK(false);
                return ti;
            }
            if (getVersion(token) != 0 && getVersion(token) == TokenVersionEnum.VERSION_1.getValue()) {
                ti.setOK(true);
                ti.setVersion(TokenVersionEnum.VERSION_1.getValue());
            } else if (getVersion(token) != 0 && getVersion(token) == TokenVersionEnum.VERSION_2.getValue()) {
                ti.setOK(true);
                ti.setUid(calculateUid(token));
                ti.setVersion(TokenVersionEnum.VERSION_2.getValue());
            }
            return ti;
        } catch (Exception e) {
            logger.error("check token error token:" + token, e);
            return null;
        }
    }

    /**
     * 校验token的版本是否是支持的版本
     *
     * @param token token
     * @return 版本校验是否通过
     */
    private static boolean checkVersion(String token) {
        byte[] sidBytes = Base62.decodeBase62(token.toCharArray());
        int v = (int) sidBytes[0];
        return TokenVersionEnum.checkKeyIsExist(String.valueOf(v));
    }

    /**
     * 检测token是否还在有效期内
     *
     * @param token token
     * @return 有效期校验是否通过
     */
    public boolean checkTokenExpDate(String token) throws Exception {
        Date createDate = getDate(token);
        long createDateTime = createDate.getTime();
        long nowTime = new Date().getTime();
        return createDateTime + ACCESS_TOKEN_EXPIRSED * 1000L > nowTime;
    }


    /**
     * 检测token的校验位是否正确
     *
     * @param token token
     * @return 校验位校验是否通过
     */
    private static boolean checkTokenSelf(String token) throws NoSuchAlgorithmException, IOException {
        byte[] sidBytes = Base62.decodeBase62(token.toCharArray());
        //取token中后4字节的自校验位
        byte[] checkByte = new byte[4];
        System.arraycopy(sidBytes, sidBytes.length - 4, checkByte, 0, checkByte.length);
        int sidCheckInt = TokenCommonUtil.bytes2Int(checkByte);

        //取前面剩余字节并根据不同版本的配置对应的密钥做MD5
        byte[] sidCheckBytes = new byte[sidBytes.length - 4];
        System.arraycopy(sidBytes, 0, sidCheckBytes, 0, sidCheckBytes.length);

        int version = (int) sidBytes[0];
        TokenVersionConfig tokenVersionConfig = TokenVersionFactory.getTokenConfig(version);
        String check_token_key = tokenVersionConfig.getCheck_token_key();
        String check = tokenVersionConfig.getCheck();
        String[] c = check.split(",");
        int i1 = Integer.valueOf(c[0]);
        int i2 = Integer.valueOf(c[1]);
        int i3 = Integer.valueOf(c[2]);
        int i4 = Integer.valueOf(c[3]);

        byte[] sidMd5Bytes = TokenCommonUtil.byteMerger(sidCheckBytes, check_token_key.getBytes());
        sidMd5Bytes = TokenCommonUtil.calculateMD5(sidMd5Bytes);
        //根据配置读MD5后的相应元素组成自校验位
        byte[] calculateCheckByte = new byte[]{sidMd5Bytes[i1], sidMd5Bytes[i2], sidMd5Bytes[i3], sidMd5Bytes[i4]};
        int calculateCheckInt = TokenCommonUtil.bytes2Int(calculateCheckByte);
        //比较是否相等
        return calculateCheckInt == sidCheckInt;
    }

    /**
     * 从token中取出创建时间
     *
     * @param token token
     * @return token的创建时间
     */
    public Date getDate(String token) {
        byte[] sidBytes = Base62.decodeBase62(token.toCharArray());
        byte[] timeBytes = new byte[4];
        System.arraycopy(sidBytes, 1, timeBytes, 0, timeBytes.length);
        int time = TokenCommonUtil.bytes2Int(timeBytes);
        return TokenCommonUtil.secondTimeToDate(time);
    }

    /**
     * 根据token获取token的版本号
     *
     * @param token token
     * @return token的版本号
     */
    private static int getVersion(String token) {
        byte[] sidBytes = Base62.decodeBase62(token.toCharArray());
        int version = (int) sidBytes[0];
        if (TokenVersionEnum.checkKeyIsExist(String.valueOf(version))) {
            return version;
        }
        return 0;
    }


    private static Long calculateUid(String token) {
        byte[] sidBytes = Base62.decodeBase62(token.toCharArray());
        //取token中间8字节的组合串
        byte[] uByte = new byte[8];
        System.arraycopy(sidBytes, 5, uByte, 0, uByte.length);
        //取第一个字节为长度
        int length = (int) uByte[0];
        byte[] iByte = new byte[length];
        System.arraycopy(uByte, 1, iByte, 0, length);
        return TokenCommonUtil.bytes2long(iByte, length);
    }

    public static void main(String[] args) throws Exception {
        String str = "161108193305000067";

        System.out.println(new Date().getTime());
        System.out.println(TokenCommonUtil.getSecondTime());

        Long uid = 161108193305000067L;



     //   Double uid = Double.parseDouble(str);


////        System.out.println(Long.toBinaryString(uid));
////        System.out.println(Long.toBinaryString(uid).length() % 8 == 0 ? Long.toBinaryString(uid).length() / 8 : Long.toBinaryString(uid).length() / 8 + 1);
//////        long ct1 = System.currentTimeMillis() / 1000;//用秒来表示
//////        System.out.println("当前系统时间戳为：" + ct1);
        String token1 = TokenServerUtil.generatorToken(uid, TokenVersionEnum.VERSION_1.getValue());
        System.out.println("生成的token1为：" + token1 + "\n长度1为：" + token1.length());
////
//////        long ct2 = System.currentTimeMillis() / 1000;//用秒来表示
//////        System.out.println("当前系统时间戳为：" + ct2);
////        String token2 = generatorToken(uid, TokenVersionEnum.VERSION_2.getValue());
////        System.out.println("生成的token2为：" + token2 + "\n长度2为：" + token2.length());
////        String wrongToken = "AVok";
////        String rightToken = "AVRibaN9MFUo9rOc9ocjXqqw";
////        byte[] rBytes = TokenCommonUtil.calculateMD5(rightToken.getBytes());
////        byte[] wBytes = TokenCommonUtil.calculateMD5(wrongToken.getBytes());
////        byte[] a = Base62.decodeBase62(rightToken.toCharArray());
////        byte[] b = Base62.decodeBase62(wrongToken.toCharArray());
////        System.out.println("rightToken MD5:" + rBytes);
////        System.out.println("wrongToken MD5:" + wBytes);
////        boolean isTrue1 = checkToken(wrongToken);
////        System.out.println("对比结果为：" + isTrue1);
////        boolean isTrue2 = checkToken(rightToken);
////        System.out.println("对比结果为：" + isTrue2);
////        TokenInfo tokenInfo = checkToken(token1);
////        if (tokenInfo != null) {
////            boolean isOK = tokenInfo.isOK();
////            System.out.println("version 1:");
////            System.out.println(isOK);
////        }
        VerifyTokenHandler handler = new VerifyTokenHandler();
   //     System.out.println(handler.getDate("AlmP5BcBAWrEdene0FiaOjl8").getTime());
        TokenInfo tokenInfo1 = handler.checkToken(token1);

        System.out.printf(tokenInfo1.toString());
////        if (tokenInfo1 != null) {
////            boolean isOK = tokenInfo1.isOK();
////            Long uidR = tokenInfo1.getUid();
////            System.out.println("version 2:");
////            System.out.println(isOK);
////            System.out.println(uidR);
////        }
    }

}

